﻿namespace com.crownpack.SharePoint
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;

    using com.crownpack.Extensions.EventHandlerExtensions;
    using com.crownpack.Extensions.SharePointExtensions;

    using Microsoft.SharePoint;

    public abstract class BaseDefinition : IDisposable
    {
        #region Fields

        bool disposed;
        Dictionary<string, Guid> _cache;

        #endregion Fields

        #region Constructors

        protected BaseDefinition( SPFeatureReceiverProperties spFeatureReceiverProperties )
        {
            Properties = spFeatureReceiverProperties;
        }

        #endregion Constructors

        #region Events

        public event EventHandler<ContentTypeDefnEventArgs> ContentTypeCreated;

        public event EventHandler<ContentTypeDefnEventArgs> ContentTypeDeleted;

        public event EventHandler<ContentTypeDefnEventArgs> ContentTypeNotCreated;

        public event EventHandler<ContentTypeDefnEventArgs> ContentTypeNotDeleted;

        public event EventHandler<CustomListDefnEventArgs> ListCreated;

        public event EventHandler<CustomListDefnEventArgs> ListDeleted;

        public event EventHandler<CustomListDefnEventArgs> ListNotCreated;

        public event EventHandler<CustomListDefnEventArgs> ListNotDeleted;

        public event EventHandler<SiteColumnDefnEventArgs> SiteColumnCreated;

        public event EventHandler<SiteColumnDefnEventArgs> SiteColumnDeleted;

        public event EventHandler<SiteColumnDefnEventArgs> SiteColumnNotCreated;

        public event EventHandler<SiteColumnDefnEventArgs> SiteColumnNotDeleted;

        #endregion Events

        #region Properties

        protected SPFeatureReceiverProperties Properties
        {
            get;
            set;
        }

        Dictionary<string, Guid> cache
        {
            get
            {
                if ( _cache == null )
                    _cache = new Dictionary<string, Guid>( );
                return _cache;
            }
        }

        #endregion Properties

        #region Methods

        public virtual void Create( )
        {
        }

        public void CreateCalculatedSiteColumns( XmlNodeList fieldDefinitions )
        {
            foreach ( XmlNode fieldDefinition in fieldDefinitions )
            {
                XmlNode definition = fieldDefinition.CloneNode( true );
                XmlNodeList fieldRefs = definition.SelectNodes( "//FieldRef" );
                if ( fieldRefs.Count > 0 )
                    foreach ( XmlNode fieldRef in fieldRefs )
                    {
                        if ( fieldRef.Attributes.Count > 0 )
                        {
                            string name = fieldRef.Attributes[ "Name" ].Value;
                            Guid? id = getFieldId( name );
                            if ( id.HasValue )
                                fieldRef.Attributes[ "ID" ].Value = id.Value.ToString( "B" );
                        }
                    }
                CreateSiteColumn( definition );
            }
        }

        public virtual void Delete( )
        {
        }

        /// <summary>
        /// This <see cref="IDisposable"/> implementation follows Microsoft coding best practices.
        /// Dispose is also initiated by the destructor of this class.
        /// Performs tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// Because the Dispose is implemented, this class can be used in a 'using' statement.
        /// </summary>
        public void Dispose( )
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        protected void CreateAssetLibrary( List<ListDefn> value )
        {
            try
            {
                using ( SPWeb spWeb = Properties.GetWeb( ) )
                {
                    foreach ( ListDefn o in value.Where( a => !string.IsNullOrEmpty( a.RootFolderName ) ) )
                        if ( !spWeb.Lists.ContainsList( o.RootFolderName ) )
                        {
                            SPList spList = spWeb.CreateAssetLibrary( o.RootFolderName, o.Title, o.Description, false );
                            spList.ContentTypesEnabled = true;
                            foreach ( SPContentType p in spList.ContentTypes )
                                p.Hidden = true;
                            foreach ( string s in o.ContentTypes )
                            {
                                SPContentType p = spWeb.GetContentTypeByName( s );
                                if ( p != null )
                                    spList.ContentTypes.Add( p );
                            }
                            if ( o.EnableVersioning )
                                spList.EnableVersioning = true;
                            if ( o.EnableMinorVersions )
                                spList.EnableMinorVersions = true;
                            if ( o.Hidden )
                                spList.Hidden = true;
                            if ( o.OnQuickLaunch )
                                spList.OnQuickLaunch = true;
                            spList.Update( );
                            ListCreated.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description ) );
                        }
                        else
                            ListNotCreated.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description, string.Format( "{0} ({1}) already contains specified list.", spWeb.Title, spWeb.Url ) ) );
                }
            }
            catch
            {
                throw;
            }
        }

        protected void CreateAssetLibrary( ListDefn value )
        {
            List<ListDefn> list = new List<ListDefn>( );
            list.Add( value );
            CreateAssetLibrary( list );
        }

        protected void CreateContentType( ContentTypeDefn value )
        {
            List<ContentTypeDefn> list = new List<ContentTypeDefn>( );
            list.Add( value );
            CreateContentTypes( list );
        }

        protected void CreateContentTypes( List<ContentTypeDefn> value )
        {
            try
            {
                foreach ( ContentTypeDefn o in value.Where( a => !string.IsNullOrEmpty( a.Name ) ) )
                {
                    using ( SPWeb spWeb = o.WebLevel ? Properties.GetWeb( ) : Properties.GetSite( ).RootWeb )
                    {
                        if ( !spWeb.ContainsContentType( o.Name ) )
                        {
                            List<SPField> fields = new List<SPField>( );
                            foreach ( FieldDefn f in o.Fields )
                            {
                                if ( f != null && !string.IsNullOrEmpty( f.Name ) && !f.BaseColumn )
                                {
                                    SPField field = spWeb.GetFieldByStaticName( f.Name );

                                    if ( field != null )
                                    {
                                        field.Required = f.Required;
                                        field.Hidden = f.Hidden;
                                        fields.Add( field );
                                    }
                                }
                            }

                            SPContentType spContentType = spWeb.CreateContentType( o.ContentTypeId, o.Group, o.Name, o.Description, false, fields.ToArray( ) );
                            foreach ( FieldDefn f in o.Fields.Where( a => a.BaseColumn ) )
                            {
                                if ( spContentType.Fields.ContainsFieldWithStaticName( f.Name ) )
                                {
                                    bool update = false;
                                    SPField field = spContentType.Fields.GetFieldByInternalName( f.Name );
                                    if ( f.Hidden )
                                    {
                                        field.Hidden = true;
                                        update = true;
                                    }
                                    if ( f.Required )
                                    {
                                        field.Required = true;
                                        update = true;
                                    }
                                    if ( update )
                                        spContentType.Update( );
                                }
                            }

                            if ( !string.IsNullOrEmpty( o.TitleName ) )
                            {
                                SPFieldLink spFieldLink = spContentType.FieldLinks[ "Title" ];
                                if ( spFieldLink != null )
                                {
                                    spFieldLink.DisplayName = o.TitleName;
                                    spContentType.Update( );
                                }
                            }

                            ContentTypeCreated.TryRaise( new ContentTypeDefnEventArgs( o.Name, o.Group, o.Description, o.ContentTypeId ) );
                        }
                        else
                            ContentTypeNotCreated.TryRaise( new ContentTypeDefnEventArgs( o.Name, o.Group, o.Description, o.ContentTypeId, string.Format( "{0} ({1}) already contains specified content type.", spWeb.Title, spWeb.Url ) ) );
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        protected void CreateList( ListDefn value )
        {
            List<ListDefn> list = new List<ListDefn>( );
            list.Add( value );
            CreateLists( list );
        }

        protected void CreateLists( List<ListDefn> value )
        {
            try
            {
                using ( SPWeb spWeb = Properties.GetWeb( ) )
                {
                    foreach ( ListDefn o in value.Where( a => !string.IsNullOrEmpty( a.RootFolderName ) ) )
                        if ( !spWeb.Lists.ContainsList( o.RootFolderName ) )
                        {
                            SPList spList = spWeb.CreateGenericList( o.RootFolderName, o.Title, o.Description, false );
                            spList.ContentTypesEnabled = true;
                            foreach ( SPContentType p in spList.ContentTypes )
                            {
                                if ( !p.Sealed )
                                {
                                    p.Hidden = true;
                                    p.Update( );
                                }
                            }
                            bool added = false;
                            foreach ( string s in o.ContentTypes )
                            {
                                SPContentType p = spWeb.GetContentTypeByName( s );
                                if ( p != null )
                                {
                                    spList.ContentTypes.Add( p );
                                    added = true;
                                }
                            }

                            if ( added )
                            {
                                List<SPContentType > contentTypeOrder = spList.RootFolder.ContentTypeOrder.ToList();
                                contentTypeOrder.Reverse();
                                if ( !string.IsNullOrEmpty( o.DefaultContentType ) )
                                {
                                    int j = -1;
                                    SPContentType top=null;
                                    for ( int i = 0; i < contentTypeOrder.Count; i++ )
                                    {
                                        if ( contentTypeOrder[ i ].Name.Equals( o.DefaultContentType ) )
                                        {
                                            j = i;
                                            top = contentTypeOrder[i];
                                            break;
                                        }
                                    }
                                    if ( j >= 0 )
                                    {
                                        contentTypeOrder.RemoveAt( j );
                                        contentTypeOrder.Insert( 0, top );
                                    }
                                }
                                spList.Update( );
                                spList.RootFolder.UniqueContentTypeOrder = contentTypeOrder;
                                spList.RootFolder.Update( );
                            }

                            if ( o.EnableVersioning )
                                spList.EnableVersioning = true;
                            if ( o.EnableMinorVersions )
                                spList.EnableMinorVersions = true;
                            if ( o.Hidden )
                                spList.Hidden = true;
                            if ( o.OnQuickLaunch )
                                spList.OnQuickLaunch = true;
                            spList.Update( );
                            ListCreated.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description ) );
                        }
                        else
                            ListNotCreated.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description, string.Format( "{0} ({1}) already contains specified list.", spWeb.Title, spWeb.Url ) ) );
                }
            }
            catch
            {
                throw;
            }
        }

        protected void CreateSiteColumn( XmlNode field )
        {
            using ( SPWeb spWeb = Properties.GetWeb( ) )
            {
                XmlAttribute a = field.Attributes[ "StaticName" ];
                if ( a != null )
                {
                    string staticName = a.Value;
                    if ( !spWeb.Fields.ContainsFieldWithStaticName( staticName ) )
                    {
                        string s = field.OuterXml;
                        spWeb.Fields.AddFieldAsXml( s );
                        SiteColumnCreated.TryRaise( new SiteColumnDefnEventArgs( staticName ) );
                    }
                    else
                        SiteColumnNotCreated.TryRaise( new SiteColumnDefnEventArgs( staticName, string.Format( "Specified column already exists within {0} ({1}).", spWeb.Title, spWeb.Url ) ) );
                }
            }
        }

        protected void CreateSiteColumns( XmlNodeList fields )
        {
            try
            {
                if ( fields != null )
                {
                    using ( SPWeb spWeb = Properties.GetWeb( ) )
                    {
                        foreach ( XmlNode field in fields )
                        {
                            XmlAttribute a = field.Attributes[ "StaticName" ];
                            if ( a != null )
                            {
                                string staticName = a.Value;
                                string exceptionMessage = string.Empty;
                                if ( !spWeb.Fields.ContainsFieldWithStaticName( staticName ) )
                                {
                                    bool ok = true;
                                    string s = field.OuterXml;
                                    const string LISTMARKER = "List=\"$";
                                    if ( s.Contains( LISTMARKER ) )
                                    {
                                        int x1 = s.IndexOf( LISTMARKER ) + LISTMARKER.Length;
                                        int x2 = s.IndexOf( "$\"", x1 );
                                        string listurl = s.Substring( x1, x2 - x1 );
                                        if ( !string.IsNullOrEmpty( listurl ) )
                                        {
                                            SPList spList = spWeb.Lists.GetListByRootFolder( listurl );
                                            if ( spList != null )
                                                s = s.Replace( string.Format( "\"${0}$\"", listurl ), string.Format( "\"{0}\"", spList.ID.ToString( "B" ) ) );
                                            else
                                            {
                                                ok = false;
                                                exceptionMessage = string.Format( "Referenced list {2} not found in {0} ({1})", spWeb.Title, spWeb.Url, listurl );
                                            }
                                        }
                                    }
                                    const string WEBIDMARKER = "WebId=\"$\"";
                                    if ( ok && s.Contains( WEBIDMARKER ) )
                                    {
                                        s = s.Replace( WEBIDMARKER, string.Format( "WebId=\"{0}\"", spWeb.ID ) );
                                    }
                                    if ( ok )
                                    {
                                        spWeb.Fields.AddFieldAsXml( s );
                                        SiteColumnCreated.TryRaise( new SiteColumnDefnEventArgs( staticName ) );
                                    }
                                    else
                                        SiteColumnNotCreated.TryRaise( new SiteColumnDefnEventArgs( staticName, exceptionMessage ) );
                                }
                                else
                                    SiteColumnNotCreated.TryRaise( new SiteColumnDefnEventArgs( staticName, string.Format( "Specified column already exists within {0} ({1}).", spWeb.Title, spWeb.Url ) ) );
                            }
                        }
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        protected void DeleteContentType( ContentTypeDefn value )
        {
            List<ContentTypeDefn> list = new List<ContentTypeDefn>( );
            list.Add( value );
            DeleteContentTypes( list );
        }

        protected void DeleteContentTypes( List<ContentTypeDefn> value )
        {
            try
            {
                using ( SPWeb spWeb = Properties.GetWeb( ) )
                {
                    foreach ( ContentTypeDefn o in value.Where( a => !string.IsNullOrEmpty( a.Name ) ) )
                    {
                        bool ok = spWeb.DeleteContentType( o.Name );
                        if ( ok )
                            ContentTypeDeleted.TryRaise( new ContentTypeDefnEventArgs( o.Name, o.Group, o.Description, o.ContentTypeId ) );
                        else
                            ContentTypeNotDeleted.TryRaise( new ContentTypeDefnEventArgs( o.Name, o.Group, o.Description, o.ContentTypeId ) );
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        protected void DeleteList( ListDefn value, bool deleteWithItems )
        {
            List<ListDefn> list = new List<ListDefn>( );
            list.Add( value );
            DeleteLists( list, deleteWithItems );
        }

        protected void DeleteLists( List<ListDefn> value, bool deleteWithItems )
        {
            try
            {
                using ( SPWeb spWeb = Properties.GetWeb( ) )
                {
                    foreach ( ListDefn o in value.Where( a => !string.IsNullOrEmpty( a.RootFolderName ) ) )
                    {
                        SPList spList = spWeb.Lists.GetListByRootFolder( o.RootFolderName );
                        if ( spList != null )
                        {
                            int itemCount = spList.ItemCount;
                            bool ok = spWeb.DeleteList( o.RootFolderName, deleteWithItems );
                            if ( ok )
                                ListDeleted.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description ) );
                            else
                                ListNotDeleted.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description, string.Format( "Specified list contains {0} items.", itemCount ) ) );
                        }
                        else
                            ListNotDeleted.TryRaise( new CustomListDefnEventArgs( o.RootFolderName, o.Title, o.Description, string.Format( "Specified list not found within {0} ({1}).", spWeb.Title, spWeb.Url ) ) );
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        protected void DeleteSiteColumns( XmlNodeList fields )
        {
            try
            {
                if ( fields != null )
                {
                    using ( SPWeb spWeb = Properties.GetWeb( ) )
                    {
                        foreach ( XmlNode field in fields )
                        {
                            XmlAttribute a = field.Attributes[ "StaticName" ];
                            if ( a != null )
                            {
                                string staticName = a.Value;
                                if ( spWeb.Fields.ContainsFieldWithStaticName( staticName ) )
                                {
                                    SPField f = spWeb.Fields.GetFieldByInternalName( staticName );
                                    if ( !f.UsedInWebContentTypes )
                                    {
                                        spWeb.Fields.Delete( staticName );
                                        SiteColumnDeleted.TryRaise( new SiteColumnDefnEventArgs( staticName ) );
                                    }
                                    else
                                        SiteColumnNotDeleted.TryRaise( new SiteColumnDefnEventArgs( staticName, string.Format( "Specified column is implemented in at least one content type within {0} ({1}).", spWeb.Title, spWeb.Url ) ) );
                                }
                                else
                                    SiteColumnNotDeleted.TryRaise( new SiteColumnDefnEventArgs( staticName, string.Format( "Specified column is not found within {0} ({1}).", spWeb.Title, spWeb.Url ) ) );
                            }
                        }
                    }
                }
            }
            catch
            {
                throw;
            }
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected void Dispose( bool disposing )
        {
            // check to see if Dispose has already been called.
            if ( !this.disposed )
            {
                // if displosing equals true, dispose all managed and unmanaged resources
                if ( disposing )
                {
                    // clear managed resources
                }
            }

            disposed = true;
        }

        void addToCache( string staticName )
        {
            using ( SPWeb spWeb = Properties.GetWeb( ) )
            {
                SPField spField = spWeb.Fields.TryGetFieldByStaticName( staticName );
                if ( spField != null )
                    cache.Add( staticName, spField.Id );
            }
        }

        Guid? getFieldId( string staticName )
        {
            if ( !cache.ContainsKey( staticName ) )
                addToCache( staticName );

            if ( cache.ContainsKey( staticName ) )
                return cache[ staticName ];
            else
                return null;
        }

        #endregion Methods
    }
}